/*
 * Core functionailty for custom pipelines
 *
 * Copyright (c) Criterion Software Limited
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   p2core.c                                                    *
 *                                                                          *
 *  Purpose :   Pipeline II (core) functionality                            *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 Includes
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include <float.h>

#include "batypes.h"
#include "balibtyp.h"
#include "badebug.h"
#include "bamemory.h"

#include "p2renderstate.h"
#include "p2define.h"
#include "p2resort.h"
#include "p2core.h"

#include "p2altmdl.h"
#include "bapipe.h"

/****************************************************************************
 Local Defines
 */

/* #define RXPACKETLOG "packet.log" */

#define P2_KEY_LO 0x00000000U  /* keyLo */
#define P2_KEY_HI 0xFFFFFFFFU  /* keyHi */

#if (defined(RWDEBUG))
#define P2CHECKHEAPPTR(ptr) \
  RWASSERT( (NULL != (ptr)) && (RWCRTISVALIDHEAPPOINTER(ptr)) )
#else /* (defined(RWDEBUG)) */
#define P2CHECKHEAPPTR(ptr) /* No op */
#endif /* (defined(RWDEBUG)) */

#define DUFFSZERO128BYTEALIGNEDMEM(_target, _bytes)                   \
MACRO_START                                                           \
{                                                                     \
    /* Duff's device for dynamic loop unrolling                       \
     * http://www.lysator.liu.se/c/duffs-device.html                  \
     */                                                               \
    const RwInt32 numQWords = ((_bytes)+15) >> 4;                     \
                                                                      \
    if (0<numQWords)                                                  \
    {                                                                 \
        static const RwUInt128 zero128 = RWZERO128 ;                  \
        RwUInt32 offset = (numQWords - 1) & ((RwUInt32)~7);           \
        RwUInt128 *mem128 = &(((RwUInt128 *)(_target))[offset]);      \
                                                                      \
        switch(numQWords & 7)                                         \
        {                                                             \
            case 0: do    {    mem128[7] = zero128;                   \
            case 7:            mem128[6] = zero128;                   \
            case 6:            mem128[5] = zero128;                   \
            case 5:            mem128[4] = zero128;                   \
            case 4:            mem128[3] = zero128;                   \
            case 3:            mem128[2] = zero128;                   \
            case 2:            mem128[1] = zero128;                   \
            case 1:            mem128[0] = zero128;                   \
                          }                                           \
            while ( (mem128 -= 8) >= (RwUInt128 *)(_target) );        \
        }                                                             \
    }                                                                 \
}                                                                     \
MACRO_STOP

#define GENERICZERO128BYTEALIGNEDMEM(mem, size)                       \
MACRO_START                                                           \
{                                                                     \
    RwUInt128 *mem128 = (RwUInt128 *)(mem);                           \
    RwInt32 numQWords = ((size)+15) >> 4;                             \
    static const RwUInt128 zero128; /* This is initialised to zero */ \
                                                                      \
    while (numQWords--)                                               \
    {                                                                 \
        *mem128++ = zero128;                                          \
    }                                                                 \
}                                                                     \
MACRO_STOP

#if (0)
#define ZERO128BYTEALIGNEDMEM(_target, _bytes) \
    DUFFSZERO128BYTEALIGNEDMEM(_target, _bytes)
#endif /* (0) */

#define ZERO128BYTEALIGNEDMEM(_target, _bytes) \
    GENERICZERO128BYTEALIGNEDMEM(_target, _bytes)

#if (defined(PLACEHOLDERS))

/*
 *  e.g to run through gcc -s to see the assembler generated by macros
 */
extern void generic_clear(void *target, int bytes);
extern void duff_clear(void *target, int bytes);

static void
generic_clear(void *target, int bytes)
{
    RWFUNCTION(RWSTRING("generic_clear"));

    GENERICZERO128BYTEALIGNEDMEM(target, bytes);

    RWRETURNVOID();
}

static void
duff_clear(void *target, int bytes)
{
    RWFUNCTION(RWSTRING("duff_clear"));

    DUFFSZERO128BYTEALIGNEDMEM(target, bytes);

    RWRETURNVOID();
}
#endif /* (defined(PLACEHOLDERS)) */

/****************************************************************************
 Globals (across program)
 */

/* Default RxHeap size */
RwInt32 _rxHeapInitialSize = RXHEAPPLATFORMDEFAULTSIZE;
/* Default Maximum number of nodes allowed in a pipeline */
RwInt32 _rxPipelineMaxNodes  = RXPIPELINEDEFAULTMAXNODES;

/****************************************************************************
 Local (static) Globals
 */

RwBool  RxPipelineInstanced   = FALSE;
RxHeap *_rxHeapGlobal         = (RxHeap *)NULL;

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Functions

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/**********************************************************************
 * Initialises RxPipeline global data:
 */

/**********************************************************************
 * De-initialises RxPipeline global data:
 */
RwBool
_rxPipelineClose(void)
{
    RWFUNCTION(RWSTRING("_rxPipelineClose"));

    /* The default pipelines are destroyed in
     * baim3d.c and bapipew.c (before this) */

    if (RxPipelineInstanced)
    {
        /* TODO: Destroy all pipelines created by the application? */

        RwFreeListDestroy(RXPIPELINEGLOBAL(pipesFreeList));
        RXPIPELINEGLOBAL(pipesFreeList) = (RwFreeList *)NULL;

        RxHeapDestroy(_rxHeapGlobal);
        _rxHeapGlobal = (RxHeap *)NULL;

        RxPipelineInstanced = FALSE;
    }

    RWRETURN(TRUE);
}

/* Use a freelist for pipelines, save allocs. Sky currently (as of
 * 4/10/2001) uses the most pipelines...  it creates 37 pipelines
 * for the core, world and all plugins. sizeof(RxPipeline) is 48
 * bytes, so 64 of them take up only 3k. */

static RwInt32 _rxPipesFreeListBlockSize = 64,
               _rxPipesFreeListPreallocBlocks = 1;
static RwFreeList _rxPipesFreeList;

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineSetFreeListCreateParams allows the developer to specify
 * how many \ref RxPipeline s to preallocate space for.
 * Call before \ref RwEngineInit.
 *
 * \param blockSize  number of entries per freelist block.
 * \param numBlocksToPrealloc  number of blocks to allocate on
 * \ref RwFreeListCreateAndPreallocateSpace.
 *
 * \see RwFreeList
 *
 */
void
RxPipelineSetFreeListCreateParams(RwInt32 blockSize, RwInt32 numBlocksToPrealloc)
{
#if 0
    /*
     * Can not use debugging macros since the debugger is not initialized before
     * RwEngineInit.
     */
    /* Do NOT comment out RWAPIFUNCTION as gnumake verify will not function */
    RWAPIFUNCTION(RWSTRING("RxPipelineSetFreeListCreateParams"));
#endif
    _rxPipesFreeListBlockSize = blockSize;
    _rxPipesFreeListPreallocBlocks = numBlocksToPrealloc;
#if 0
    RWRETURNVOID();
#endif
}

/*****************************************************************************/

RwBool
_rxPipelineOpen(void)
{
    RWFUNCTION(RWSTRING("_rxPipelineOpen"));

    /* The default pipelines are created in
     * baim3d.c and bapipew.c (after this) */

    if (!RxPipelineInstanced)
    {
        /* Temporary... ish.... measure... not sure where we'll go eventually */
        _rxHeapGlobal = RxHeapCreate(_rxHeapInitialSize);
        RWASSERT(NULL != _rxHeapGlobal);
        if      (NULL == _rxHeapGlobal)
        {
            RwDebugSendMessage(rwDEBUGMESSAGE,
                               "_rxPipelineOpen",
                               "Could not create global pipeline heap.");
            RWRETURN(FALSE);
        }

        RXPIPELINEGLOBAL(pipesFreeList) = RwFreeListCreateAndPreallocateSpace(
            sizeof(RxPipeline), _rxPipesFreeListBlockSize, sizeof(RwUInt32),
            _rxPipesFreeListPreallocBlocks, &_rxPipesFreeList,
            rwID_PIPEMODULE | rwMEMHINTDUR_GLOBAL);
        RWASSERT(NULL != RXPIPELINEGLOBAL(pipesFreeList));
        if      (NULL == RXPIPELINEGLOBAL(pipesFreeList))
        {
            RwDebugSendMessage(rwDEBUGMESSAGE,
                               "_rxPipelineOpen",
                               "Could not create RxPipeline freelist.");
            RxHeapDestroy(_rxHeapGlobal);
            _rxHeapGlobal = (RxHeap *)NULL;
            RWRETURN(FALSE);
        }

        /* Ensure nobody changes this after startup */
        RXPIPELINEGLOBAL(maxNodesPerPipe) = _rxPipelineMaxNodes;

        RxRenderStateVectorSetDefaultRenderStateVector(
            &RXPIPELINEGLOBAL(defaultRenderState));

        /* Not implemented yet */
        RXPIPELINEGLOBAL(allPipelines).link.prev = (RwLLLink *)NULL;
        RXPIPELINEGLOBAL(allPipelines).link.next = (RwLLLink *)NULL;

        RxPipelineInstanced = TRUE;

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

/*****************************************************************************/
RxPipelineNode *
PipelineNodeDestroy(RxPipelineNode * node, RxPipeline *pipeline)
{
    RWFUNCTION(RWSTRING("PipelineNodeDestroy"));

    RWASSERT(node != NULL);
    RWASSERT(pipeline != NULL);
    RWASSERT(node->nodeDef != NULL);

    if (FALSE == pipeline->locked)
    {
        /* This is only called for unlocked pipelines from RxPipelineDestroy().
         * In this case, we don't bother 'freeing' the pipeline node, we just
         * call its deinit callbacks and free its initialisationData.
         * Remember, nodeDef 'InputPipesCnt' reference counts are to UNLOCKED
         * pipeline nodes only, and also that nodeinit/pipelinenodeinit are
         * called in unlock. */

        /* Allow the node to deinitialise any memory it's set up */
        if (node->nodeDef->nodeMethods.pipelineNodeTerm != NULL)
        {
            node->nodeDef->nodeMethods.pipelineNodeTerm(node);
        }

        node->nodeDef->InputPipesCnt--;
        RWASSERT(0 <= node->nodeDef->InputPipesCnt);
        if (0 == node->nodeDef->InputPipesCnt)
        {
            /* nodeterm (called for the last pipelinenode
             * referencing a given node definition) and
             * free the node definition if it is 'editable' */
            if (node->nodeDef->nodeMethods.nodeTerm != NULL)
            {
                node->nodeDef->nodeMethods.nodeTerm(node->nodeDef);
            }
            if (node->nodeDef->editable)
            {
                RWASSERT(RWCRTISVALIDHEAPPOINTER(node->nodeDef));
                RwFree(node->nodeDef);
                node->nodeDef = (RxNodeDefinition *)NULL;
            }
        }

        /* Free initialisationData if present */
        if (node->initializationData != NULL)
        {
            RWASSERT(RWCRTISVALIDHEAPPOINTER(node->initializationData));
            RwFree(node->initializationData);
            node->initializationData = NULL;
            node->initializationDataSize = 0;
        }

        /* Par-a-noi-a a-a-a... pushing down on me... */
        memset(node, 0, sizeof(RxPipelineNode));
    }
    else
    {
        RwInt32 nodeIndex;

        /* The pipeline is locked, so its superblock contains only
         * the three (conservatively-sized - see RxPipelineLock())
         * arrays for pipeline nodes, then outputs, then topSortData.
         * We'll be removing this node, so we will have to shuffle
         * these arrays to compact them and fix up pointers. */

        /* Free initialisationData if present */
        if (node->initializationData != NULL)
        {
            RWASSERT(RWCRTISVALIDHEAPPOINTER(node->initializationData));
            RwFree(node->initializationData);
            node->initializationData = NULL;
            node->initializationDataSize = 0;
        }

        /* For a LOCKED pipeline, inpipes only references nodes in other
         * (UNLOCKED)pipelines (RxPipelineLock() decrements reference
         * counts). nodeDef->InputPipesCnt will have been decremented
         * and nodeterm called in lock() already (if (inpipes == 0))
         * We free the nodeDef here [rather than in lock()] so that if
         * people lock and then unlock (without removing this node), and
         * InputPipesCnt goes to zero inbetween, the nodeDef isn't
         * deleted. Editable nodeDefs ONLY get destroyed by this func. */
        RWASSERT(0 <= node->nodeDef->InputPipesCnt);
        if (0 == node->nodeDef->InputPipesCnt)
        {
            /* Looks like this node def was only
             * referenced by node(s) in this pipeline */
            if (node->nodeDef->editable)
            {
                RWASSERT(RWCRTISVALIDHEAPPOINTER(node->nodeDef));
                RwFree(node->nodeDef);
                node->nodeDef = (RxNodeDefinition *)NULL;
            }
        }

        nodeIndex = node - &(pipeline->nodes[0]);
        RWASSERT((nodeIndex >= 0) && ((RwUInt32)nodeIndex < pipeline->numNodes));
        if ((RwUInt32)nodeIndex < (pipeline->numNodes - 1))
        {
            /* Shufflin', m-bebbeh! */
            RxPipelineNodeTopSortData *topSortData, *nextTopSortData;
            RwUInt32 *output, *nextOutput;
            RwUInt32 i, j;

            /* outputs */
            output = (RwUInt32 *)
                &(pipeline->nodes[RXPIPELINEGLOBAL(maxNodesPerPipe)]);
            output += RXNODEMAXOUTPUTS*nodeIndex;
            nextOutput = output + RXNODEMAXOUTPUTS;
            for (i = nodeIndex;i < (pipeline->numNodes - 1);i++)
            {
                memcpy(output, nextOutput, sizeof(RwUInt32)*RXNODEMAXOUTPUTS);
                output = nextOutput;
                nextOutput += RXNODEMAXOUTPUTS;
            }

            /* topSortData */
            output = (RwUInt32 *)
                &(pipeline->nodes[RXPIPELINEGLOBAL(maxNodesPerPipe)]);
            topSortData = (RxPipelineNodeTopSortData *)
                &(output[RXNODEMAXOUTPUTS*RXPIPELINEGLOBAL(maxNodesPerPipe)]);
            nextTopSortData = topSortData + 1;
            for (i = nodeIndex;i < (pipeline->numNodes - 1);i++)
            {
                memcpy(topSortData,
                       nextTopSortData,
                       sizeof(RxPipelineNodeTopSortData));
                topSortData = nextTopSortData;
                nextTopSortData++;
            }

            /* Pipeline nodes (inc. fixup) */
            for (i = nodeIndex;i < (pipeline->numNodes - 1);i++)
            {
                memcpy(&(pipeline->nodes[i]),
                       &(pipeline->nodes[i + 1]),
                       sizeof(RxPipelineNode));
                pipeline->nodes[i].outputs -= RXNODEMAXOUTPUTS;
                pipeline->nodes[i].topSortData--;
            }

            /* Node positions have changed, fix up output indices */
            for (i = 0;i < (pipeline->numNodes - 1);i++)
            {
                for (j = 0;j < pipeline->nodes[i].numOutputs;j++)
                {
                    if (pipeline->nodes[i].outputs[j] >= (RwUInt32)nodeIndex)
                    {
                        if (pipeline->nodes[i].outputs[j] == (RwUInt32)nodeIndex)
                        {
                            /* This node is gone! */
                            pipeline->nodes[i].outputs[j] = (RwUInt32)-1;
                        }
                        else
                        {
                            /* This node has moved down one space. */
                            pipeline->nodes[i].outputs[j]--;
                        }
                    }

                }
            }
        }
        else
        {
            /* Kewl, it's the last node, no shuffwin' wekwyyaahhed */
        }
    }

    /* Only this and _NodeCreate() should ever modify numNodes
     * (okay, so RxPipelineClone() might do as well) */
    pipeline->numNodes--;

    RWRETURN(node);
}

/*****************************************************************************
 _rxPipelineExecuteNode

 This is purely for debugging purposes. It wraps the call to a node so that
 we can output node entry/exit debug-stream messages, set a global for the
 'current node' and 'current pipeline', etc...

 In release mode this is a macro that boils away to nothing.
*/

#if (defined(RWDEBUG) && defined(RXMONITOR))

static              RwBool
PL2PipelineExecuteNodeMonitor(RxPipelineNode * currentNode,
                              RxPipelineNodeParam * params)
{
    RwBool              result = FALSE;
    const RxNodeDefinition *const NodeRef;
    const RwChar       *const name;

    RWFUNCTION(RWSTRING("PL2PipelineExecuteNodeMonitor"));

    RWASSERT(currentNode != NULL);
    RWASSERT(memory != NULL);

    NodeRef = currentNode->nodeRef->nodeRef;
    name = NodeRef->name;

    RWMESSAGE(("Entering Node", name));

    result = NodeRef->nodeMethods.nodeBody(currentNode, params);

    if (TRUE == result)
    {
        RWMESSAGE(("Exited node with return value TRUE", name));
    }
    else
    {
        RWMESSAGE(("Exited node with return value FALSE", name));
    }

    RWRETURN(result);
}

#define _rxPipelineExecuteNode(node, mem, data)                  \
    PL2PipelineExecuteNodeMonitor(node, mem, data)

#else /* (defined(RWDEBUG) && defined(RXMONITOR)) */

#define _rxPipelineExecuteNode(_node, _params)                  \
    (node->nodeRef->nodeRef->nodeMethods.nodeBody(_currentNode, _params))

#endif /* (defined(RWDEBUG) && defined(RXMONITOR)) */

#if (defined(RWDEBUG))

#if (defined(RXPACKETLOG))

#define RXPACKETLOGOPEN(_result, _name) \
  _result = fopen(_name, "at")

#define RXPACKETLOGFPRINTF(_args) \
   fprintf _args

#define RXPACKETLOGCLOSE(_file) \
   fclose(_file)

#endif /* defined(RXPACKETLOG) */

#if (!defined(RXPACKETLOGOPEN))
#define RXPACKETLOGOPEN(_result, _name) /* No op */
#endif /* (!defined(RXPACKETLOGOPEN)) */

#if (!defined(RXPACKETLOGFPRINTF))
#define RXPACKETLOGFPRINTF(_args) /* No op */
#endif /* (!defined(RXPACKETLOGFPRINTF)) */

#if (!defined(RXPACKETLOGCLOSE))
#define RXPACKETLOGCLOSE(_file) /* No op */
#endif /* (!defined(RXPACKETLOGCLOSE)) */

#if (defined(WIN32))

/*
 * IsBadReadPtr() is defined in
 * /Program Files/Microsoft Visual Studio/VC98/Include/WINBASE.H
 */
#define RWISBADREADPTR(lp,ucb) IsBadReadPtr(lp,ucb)
#endif /* (defined(WIN32)) */

#if (!defined(RWISBADREADPTR))
#define RWISBADREADPTR(lp,ucb) (!(RWCRTISVALIDPOINTER(lp,ucb,FALSE)))
#endif /* (!defined(RWISBADREADPTR)) */

/*****************************************************************************
 RXPIPELINESANITYCHECKPACKETS(node)

 This macro is called from RxPipelineExecute() when RWDEBUG is defined.

 It will RWASSERT() on a couple of "broken cluster" conditions.

 If RWDEBUG and RXPACKETLOG are both defined  -- and the latter is defined to be
 a filename, e.g. "packet.log" --, you get a record of packets flowing down the
 pipe.

 *****************************************************************************/

#define VALIDATEEXTERNAL(cl)                                                \
MACRO_START                                                                 \
{                                                                           \
    /* external */                                                          \
    RwBool ValidExternal = !RWISBADREADPTR((cl->data),                      \
                                            minBlockSize);                  \
                                                                            \
    RWASSERT(ValidExternal);                                                \
    if(!ValidExternal)                                                      \
    {                                                                       \
        RXPACKETLOGFPRINTF((fp,                                             \
                          "%s(%d): 'VALID' CLUSTER HAS BAD DATA PTR!!!\n",  \
                              __FILE__, __LINE__));                         \
    }                                                                       \
}                                                                           \
MACRO_STOP

#if (!defined(DISABLERWHEAP))
#define VALIDATEINTERNAL(ptr)                                               \
MACRO_START                                                                 \
{                                                                           \
    /* internal */                                                          \
    rxHeapBlockHeader *blkhdr =                                             \
        ((rxHeapBlockHeader *) (cl->data)) - 1;                             \
    RwBool ValidInternal =                                                  \
        ( blkhdr->size >= minBlockSize &&                                   \
          blkhdr->freeEntry == NULL &&                                      \
          blkhdr->prev->next == blkhdr &&                                   \
          blkhdr->next->prev == blkhdr );                                   \
                                                                            \
    RWASSERT(ValidInternal);                                                \
                                                                            \
    if(!ValidInternal)                                                      \
    {                                                                       \
        RXPACKETLOGFPRINTF((fp,                                           \
                          "%s(%d): 'VALID' CLUSTER HAS BAD DATA PTR!!!\n",  \
                              __FILE__, __LINE__));                         \
    }                                                                       \
}                                                                           \
MACRO_STOP

#endif /*(!defined(DISABLERWHEAP)) */

#if (!defined(VALIDATEINTERNAL))
#define VALIDATEINTERNAL(ptr)  /* No op */
#endif /* (!defined(VALIDATEINTERNAL)) */

#if (defined(RXPACKETLOG))
#define FILEDECLARE(_fp) FILE *_fp
#else /* (defined(RXPACKETLOG)) */
#define FILEDECLARE(_fp)       /* No op */
#endif /* (defined(RXPACKETLOG)) */

#define RXPIPELINESANITYCHECKPACKETS(node)                                  \
MACRO_START                                                                 \
{                                                                           \
    RxPipelineCluster **slotClusterRefs;                                    \
    RxPacket *pk;                                                           \
    int packetindex;                                                        \
    FILEDECLARE(fp);                                                        \
                                                                            \
    RWASSERT(NULL != node);                                                 \
                                                                            \
    slotClusterRefs = (node)->nodeRef->slotClusterRefs;                     \
                                                                            \
    RXPACKETLOGOPEN(fp, RXPACKETLOG);                                       \
                                                                            \
    RXPACKETLOGFPRINTF((fp, "%s\n", (node)->nodeRef->nodeRef->name));       \
                                                                            \
    for (pk = (node)->inputHead, packetindex = 0;                           \
         pk != NULL;                                                        \
         pk = pk->prev,  packetindex++)                                     \
    {                                                                       \
        int n;                                                              \
                                                                            \
        RXPACKETLOGFPRINTF((fp, "  %d\n", packetindex));                    \
                                                                            \
        for (n = 0; n < pk->numClusters; n++)                               \
        {                                                                   \
            if ( NULL != slotClusterRefs[n] )                               \
            {                                                               \
                RxCluster *cl = &pk->clusters[n];                           \
                                                                            \
                RXPACKETLOGFPRINTF((fp,                                     \
                                      "    %36s",                           \
                                      slotClusterRefs[n]->clusterRef->name));\
                                                                            \
                if ( !(cl->flags & rxCLFLAGS_CLUSTERVALID) )                \
                {                                                           \
                    RXPACKETLOGFPRINTF((fp, " NV\n"));                      \
                }                                                           \
                else                                                        \
                {                                                           \
                    RwUInt32 minBlockSize;                                  \
                    RwBool numUsed_within_numAlloced;                       \
                                                                            \
                    RXPACKETLOGFPRINTF((fp,                                 \
                                          "  V %s 0x%08X 0x%04X %d%s/%d\n", \
                                          ( cl->flags & rxCLFLAGS_EXTERNAL ) ?\
                                          " X" : "NX",                      \
                                          (RwUInt32) cl->data,              \
                                          cl->stride,                       \
                                          cl->numUsed,                      \
                                          (cl->numUsed > cl->numAlloced) ?  \
                                          "!!!" : "",                       \
                                          cl->numAlloced));                 \
                                                                            \
                    numUsed_within_numAlloced =                             \
                        ( cl->numUsed <= cl->numAlloced );                  \
                                                                            \
                    RWASSERT(numUsed_within_numAlloced);                    \
                                                                            \
                    minBlockSize =                                          \
                        ~3U & ( (((RwUInt32) cl->numAlloced) *              \
                                 ((RwUInt32) cl->stride)) + 3U ) ;          \
                                                                            \
                    if ( cl->flags & rxCLFLAGS_EXTERNAL )                   \
                        VALIDATEEXTERNAL(cl);                               \
                    else                                                    \
                        VALIDATEINTERNAL(cl);                               \
                }                                                           \
            }                                                               \
        }                                                                   \
    }                                                                       \
                                                                            \
    RXPACKETLOGCLOSE(fp);                                                   \
}                                                                           \
MACRO_STOP

#endif /* (defined(RWDEBUG)) */

#if (!defined(RXPIPELINESANITYCHECKPACKETS))
#define RXPIPELINESANITYCHECKPACKETS(node) /* No op */
#endif /* (!defined(RXPIPELINESANITYCHECKPACKETS)) */

/**
 * \ingroup rwcoregeneric
 * \ref RxHeapGetGlobalHeap
 * returns the global PowerPipe heap.
 *
 * The pipeline execution code maintains an execution heap that is
 * tightly bound to the current invocation of \ref RxPipelineExecute .
 *
 * This heap is used to rapidly service allocations for "transient" memory;
 * such allocations are not preserved between pipeline executions - the
 * heap is cleared down on exit from \ref RxPipelineExecute, with a call
 * to \ref RxHeapReset.
 *
 * \ref RxClusterInitializeData and \ref RxClusterResizeData, for example,
 * use the execution heap to allocate cluster memory.
 *
 * The returned heap of type \ref RxHeap
 *  can be used in calls to
 * \ref RxHeapAlloc, \ref RxHeapRealloc and \ref RxHeapFree. During the
 * execution of the node body method of a \ref RxPipelineNode,
 * the current heap - we may have multiple heaps eventually to execute
 * more than one pipe simultaneously on multiprocessor systems -  may
 * be obtained through \ref RxPipelineNodeParamGetHeap.
 *
 * Use of execution heap:
 * \verbatim
   {
           RxHeap *heap;
           int *intArray;

           heap = RxHeapGetGlobalHeap();

           intArray = (int *) RxHeapAlloc(heap, 200 * sizeof(int));

           intArray[199] = 3;

           RxHeapFree(heap, intArray);
   }
   \endverbatim
 *
 * \return A pointer to the global pipeline execution heap
 *
 * \see RxPipelineExecute
 * \see RxPipelineNodeParamGetHeap
 * \see RxHeapAlloc
 * \see RxHeapRealloc
 * \see RxHeapFree
 */
RxHeap             *
RxHeapGetGlobalHeap(void)
{
    RWAPIFUNCTION(RWSTRING("RxHeapGetGlobalHeap"));
    RWASSERT(RxPipelineInstanced);

    RWRETURN(_rxHeapGlobal);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPacketCreate
 * creates an empty packet.
 *
 * This function will fail if there is insufficient memory on the
 * pipeline execution heap or if a packet is already in existence
 * (owing to the nested pipeline executed model, only one packet
 * exists at a time, see \ref RxPacketFetch for details).
 *
 * Data for all clusters in the created packet will be flagged invalid
 * !(RxCluster.flags & rxCLFLAGS_CLUSTERVALID). Use \ref RxClusterLockWrite
 * then \ref RxClusterInitializeData, \ref RxClusterSetExternalData or
 * \ref RxClusterSetData to initialize clusters as required.
 *
 * This function is for use within node body methods.
 *
 * \param self  A pointer to the calling pipeline node
 *
 * \return A pointer to an empty packet on success, otherwise NULL
 *
 * \see RxPacketDestroy
 * \see RxPacketFetch
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 */
RxPacket           *
RxPacketCreate(RxPipelineNode * self)
{
    RxPacket           *createdPacket;

    RWAPIFUNCTION(RWSTRING("RxPacketCreate"));

    RWASSERT(RxPipelineInstanced);
    RWASSERT(NULL != (self));
    RWASSERT(NULL != (_rxExecCtxGlobal.pipeline));

    createdPacket = _rxPacketCreate(_rxExecCtxGlobal.pipeline);
    RWASSERT(NULL != createdPacket);

    createdPacket->inputToClusterSlot = self->inputToClusterSlot;
    createdPacket->slotsContinue = self->slotsContinue;
    createdPacket->slotClusterRefs = self->slotClusterRefs;

    RWRETURN(createdPacket);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterSetStride
 * sets a cluster's stride (the increment
 * in bytes from one element of the data array to the next).
 *
 * This function only stores the stride value; it does not reorganize the
 * data array, nor does it resize it. It is common practice to follow
 * \ref RxClusterSetStride with an \ref RxClusterResizeData. When creating a
 * cluster, it is more natural to use the \ref RxClusterInitializeData call,
 * which sets the stride of, and allocates memory for, the data array.
 *
 * \param cluster  A pointer to the cluster to act upon
 * \param stride  An \ref RwInt32 equal to the new stride for the cluster's data
 *
 * \return Pointer to the modified cluster on success, or NULL if there is an error
 *
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterInitializeData
 * \see RxClusterResizeData
 * \see RxClusterDestroyData
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 */
RxCluster          *
RxClusterSetStride(RxCluster * cluster, RwInt32 stride)
{
    RWAPIFUNCTION(RWSTRING("RxClusterSetStride"));

    RWASSERT(NULL != cluster);
    RWASSERT(stride > 0);
    RWASSERT(stride <= 65535);

    /* TODO: er... MACRO anyone ? */

    cluster->stride = (RwUInt16) stride;

    RWRETURN(cluster);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterSetExternalData
 * initializes a cluster as referencing
 * external data.
 *
 * Clusters are the means by which packets convey data. A packet will usually
 * contain a number of clusters, e.g. { RenderState, ObjVerts, Triangles }.
 *
 * Clusters are classified as internal or external (to pipeline execution).
 * Nodes treat internal or external clusters in exactly the same fashion;
 * indeed, as a rule, nodes need not know whether a given cluster is
 * internal or external. This function sets the data to EXTERNAL.
 *
 * INTERNAL clusters: A cluster created by \ref RxClusterInitializeData is
 * internal - the cluster data is allocated from the pipeline execution
 * heap, and the cluster may be resized and its data modified as the packet
 * flows down the pipeline. When the containing packet is destroyed, or the
 * cluster is terminated - because subsequent nodes have no interest in it,
 * as identified by dependency chasing during \ref RxLockedPipeUnlock - the
 * cluster data is freed.
 *
 * EXTERNAL clusters: These are used to convey a reference to data external
 * to the pipeline execution heap. Should a node attempt to modify (i.e.
 * call \ref RxClusterLockWrite) or resize (\ref RxClusterResizeData) an
 * external cluster, then the cluster will be transparently "internalized"
 * (the referenced data will be copied into the pipeline execution heap,
 * and the cluster will become internal). In the event of internalization,
 * it is the copied data that is subsequently propagated. Note that this
 * scheme makes it essential that you use \ref RxClusterLockWrite for a
 * cluster instead of \ref RxClusterLockRead, if you are subsequently to
 * edit the cluster's data.
 *
 * Note that in some cases it may be useful to flag cluster data that is
 * allocated from the pipeline execution heap as external. Doing so will
 * protect the original data array from modification by any subsequent
 * nodes in the pipeline, which might be useful if the same array is to
 * be used in multiple packets that a node creates.
 *
 * Non-array data is treated as a single element array; it is still
 * necessary to correctly set the stride for non-array data.
 *
 * \param cluster  A pointer to the cluster to act upon
 * \param data  A pointer to the data to reference
 * \param stride  An \ref RwInt32 equal to the stride of data array (increment
 *  in bytes from one element of the data array to the next)
 * \param numElements  An \ref RwInt32 equal to the number of elements in
 *  the data array (or 1 for non-array data)
 *
 * \return A pointer to the modified cluster on success or NULL if there
 *  is an error
 *
 * \see RxClusterInitializeData
 * \see RxClusterResizeData
 * \see RxClusterDestroyData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetData
 * \see RxClusterSetData
 */
RxCluster          *
RxClusterSetExternalData(RxCluster * cluster,
                         void *data,
                         RwInt32 stride, RwInt32 numElements)
{
    RWAPIFUNCTION(RWSTRING("RxClusterSetExternalData"));

    RWASSERT(RxPipelineInstanced);
    RWASSERT(NULL != cluster);
    RWASSERT(NULL != data);
    RWASSERT(stride > 0);
    RWASSERT(stride <= 65535);
    RWASSERT(numElements > 0);

    /* TODO: see setData below */
    if ((cluster->data != NULL) &&
        (!(cluster->flags & rxCLFLAGS_EXTERNAL)) &&
        (cluster->data != data))
    {
        /* If current and incoming data are equal, we're essentially just
         * setting a flag, and maybe changing stride, numalloced etc... */

        /* Free the previous block of data */
        P2CHECKHEAPPTR(cluster->data);
        RxHeapFree(_rxHeapGlobal, cluster->data);
        cluster->data = NULL;
    }

    cluster->data = data;
    cluster->currentData = data;
    cluster->stride = (RwUInt16) stride;
    /* The cluster now has data */
    cluster->flags |= (rxCLFLAGS_EXTERNAL | rxCLFLAGS_CLUSTERVALID);
    cluster->numAlloced = numElements;
    cluster->numUsed = numElements;

    RWRETURN(cluster);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterSetData
 * sets a cluster's data to an existing data block.
 *
 * Clusters are the means by which packets convey data. A packet will usually
 * contain a number of clusters, e.g. { RenderState, ObjVerts, Indices }.
 *
 * Clusters are classified as internal or external (to pipeline execution).
 * Nodes treat internal or external clusters in exactly the same fashion;
 * indeed, as a rule, nodes need not know whether a given cluster is
 * internal or external. This function sets the data to INTERNAL.
 *
 * INTERNAL clusters: A cluster created by \ref RxClusterInitializeData is
 * internal - the cluster data is allocated from the pipeline execution
 * heap, and the cluster may be resized and its data modified as the packet
 * flows down the pipeline. When the containing packet is destroyed, or the
 * cluster is terminated - because subsequent nodes have no interest in it,
 * as identified by dependency chasing during \ref RxLockedPipeUnlock - the
 * cluster data is freed.
 *
 * EXTERNAL clusters: These are used to convey a reference to data external
 * to the pipeline execution heap. Should a node attempt to modify (i.e.
 * call \ref RxClusterLockWrite) or resize (\ref RxClusterResizeData) an
 * external cluster, then the cluster will be transparently "internalized"
 * (the referenced data will be copied into the pipeline execution heap,
 * and the cluster will become internal). In the event of internalization,
 * it is the copied data that is subsequently propagated. Note that this
 * scheme makes it essential that you use \ref RxClusterLockWrite for a
 * cluster instead of \ref RxClusterLockRead, if you are subsequently to
 * edit the cluster's data.
 *
 * Non-array data is treated as a single element array; it is still
 * necessary to correctly set the stride for non-array data.
 *
 * \param cluster  A pointer to the cluster to act upon
 * \param data  A pointer to the data to reference
 * \param stride An \ref RwInt32 equal to the stride of data array (increment
 *  in bytes from one element of the data array to the next)
 * \param numElements  An \ref RwInt32 equal to the number of elements in
 *  data array (or 1 for non-array data)
 *
 * \return A pointer to the modified cluster on success, or NULL if there is an error
 *
 * \see RxClusterInitializeData
 * \see RxClusterResizeData
 * \see RxClusterDestroyData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetExternalData
 */
RxCluster          *
RxClusterSetData(RxCluster * cluster,
                 void *data, RwInt32 stride, RwInt32 numElements)
{
    RWAPIFUNCTION(RWSTRING("RxClusterSetData"));

    RWASSERT(RxPipelineInstanced);
    RWASSERT(NULL != cluster);
    RWASSERT(NULL != data);
    RWASSERT(stride > 0);
    RWASSERT(stride <= 65535);
    RWASSERT(numElements > 0);

    /* TODO:
     * (implement this function (a) as a macro
     *  and (b) so it takes a flag parameter (external, external modifiable
     *  or local - add that one) - so we only need setData and not
     *  setExternalData)
     */
    if ((cluster->data != NULL) &&
        (!(cluster->flags & rxCLFLAGS_EXTERNAL)) &&
        (cluster->data != data))
    {
        /* If current and incoming data are equal, we're essentially just
         * setting a flag, and maybe changing stride, numalloced etc... */
        /* Free the previous block of data */
        P2CHECKHEAPPTR(cluster->data);
        RxHeapFree(_rxHeapGlobal, cluster->data);
        cluster->data = NULL;
    }

    cluster->data = data;
    cluster->currentData = data;
    cluster->stride = (RwUInt16) stride;
    cluster->flags |= rxCLFLAGS_CLUSTERVALID; /* The cluster now has data */
    cluster->numAlloced = numElements;
    cluster->numUsed = numElements;

    RWRETURN(cluster);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterInitializeData
 * creates a cluster's data array.
 *
 * In the same manner as the run-time library alloc() call,
 * RxClusterInitializeData can be used to allocate cluster data. However,
 * it is probably best to think of this function's purpose as to
 * "(re)initialize a cluster's data" - it sets the cluster's stride (values
 * greater than zero are valid) and allocates data. If the cluster already
 * has data then it will be freed. If you know that you do not need the old
 * data then it is better to call this function rather than
 * \ref RxClusterResizeData, because the copy of the data that that function
 * will do is entirely unnecessary and may be costly.
 *
 * Both the stride and number of elements specified must be greater than zero.
 *
 * \param cluster  A pointer to the cluster to act upon
 * \param numElements  An \ref RwUInt32 equal to the number of elements
 * to alloc
 * \param stride       An \ref RwUInt32 equal to the stride of the
 * cluster's data array
 *
 * \return Pointer to the modified cluster on success, or NULL if there is an error
 *
 * \see RxClusterResizeData
 * \see RxClusterDestroyData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 * \see RxClusterSetStride
 */
RxCluster          *
RxClusterInitializeData(RxCluster * cluster,
                        RwUInt32 numElements, RwUInt16 stride)
{
    void               *allocResult;
    RwUInt32            size = numElements * stride;

    RWAPIFUNCTION(RWSTRING("RxClusterInitializeData"));

    RWASSERT(RxPipelineInstanced);
    RWASSERT(cluster != NULL);
    RWASSERT(numElements > 0);
    RWASSERT(stride > 0);

    if ((cluster->data != NULL) &&
        (!(cluster->flags & rxCLFLAGS_EXTERNAL)))
    {
        RxHeapFree(_rxHeapGlobal, cluster->data);
    }

    allocResult = RxHeapAlloc(_rxHeapGlobal, size);

#if (defined(RWDEBUG))
    if (allocResult == NULL)
    {
        RWERROR((E_RW_NOMEM, size));
        RWRETURN((RxCluster *)NULL);
    }
#endif /* (defined(RWDEBUG)) */

    P2CHECKHEAPPTR(allocResult);

    cluster->data = allocResult;
    cluster->currentData = allocResult;

    cluster->numAlloced = numElements;
    cluster->numUsed = 0;

    /* We definitely have data now */
    cluster->flags |= rxCLFLAGS_CLUSTERVALID;
    cluster->stride = stride;

    RWRETURN(cluster);

}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterResizeData
 *  resizes a cluster's data array.
 *
 * In the same manner as the run-time library realloc() call,
 * \ref RxClusterResizeData can be used to reallocate cluster data.
 * \ref RxClusterDestroyData should be used to deallocate data, not this
 * function. Equally, data should be initially allocated by
 * \ref RxClusterInitializeData, not this function.
 *
 * The number of elements specified must be greater than zero.
 *
 * \param cluster  A pointer to the cluster to act upon
 * \param numElements  An \ref RwUInt32 equal to the new number of elements
 * in the cluster's data array; should be greater than zero
 *
 * \return Pointer to the modified cluster on success, or NULL if there is an error
 *
 * \see RxClusterInitializeData
 * \see RxClusterDestroyData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 * \see RxClusterSetStride
 */
RxCluster          *
RxClusterResizeData(RxCluster * cluster, RwUInt32 numElements)
{
    RwUInt32            newSize;
    void               *allocresult;

    RWAPIFUNCTION(RWSTRING("RxClusterResizeData"));

    /* The cluster must be valid (i.e one of RxClusterLockRead/Write()
     * has been called)
     *
     * We need to resolve when/why these flags are set/checked and
     * implement something consistent
     *  - the new clusters bit-field thing looks promising [6.6.2000]
     *
     * RWASSERT(cluster->flags & rxCLFLAGS_CLUSTERVALID);
     */

    RWASSERT(RxPipelineInstanced);
    RWASSERT(cluster != NULL);
    RWASSERT(numElements != 0);
    RWASSERT(!(cluster->flags & rxCLFLAGS_EXTERNAL));
    RWASSERT(cluster->stride > 0);
    RWASSERT(cluster->data != NULL);

    newSize = numElements * ((RwUInt32) cluster->stride);

    P2CHECKHEAPPTR(cluster->data);

    allocresult = RxHeapRealloc(_rxHeapGlobal, cluster->data, newSize, TRUE);
#if (defined(RWDEBUG))
    if (allocresult == NULL)
    {
        RWRETURN((RxCluster *) NULL);
    }
#endif /* (defined(RWDEBUG)) */

    P2CHECKHEAPPTR(allocresult);

#if (defined(RWDEBUG) && defined(RXMONITOR))
    if (allocresult != cluster->data)
    {
        RWMESSAGE(("%ld bytes copied!", newSize));
    }
#endif /* (defined(RWDEBUG) && defined(RXMONITOR)) */

    cluster->data = allocresult;
    cluster->currentData = allocresult;

    /* We definitely have data now */
    cluster->flags |= rxCLFLAGS_CLUSTERVALID;

    if (cluster->numUsed > numElements)
    {
        cluster->numUsed = numElements;
    }
    cluster->numAlloced = numElements;

    RWRETURN(cluster);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterDestroyData
 * destroys a cluster's data array.
 *
 * In the same manner as the run-time library free() call,
 * RxClusterDestroyData can be used to deallocate cluster data. A cluster
 * should have some data to free on entering this function, naturally - if it
 * is external data, it won't actually be freed, but the cluster's pointer to
 * the data will be cleared, as will its flags.
 *
 * \param cluster  A pointer to the cluster to act upon
 *
 * \return A NULL cluster pointer
 *
 * \see RxClusterInitializeData
 * \see RxClusterResizeData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 * \see RxClusterSetStride
 */
RxCluster          *
RxClusterDestroyData(RxCluster * cluster)
{
    RWAPIFUNCTION(RWSTRING("RxClusterDestroyData"));

    RWASSERT(RxPipelineInstanced);
    RWASSERT(cluster != NULL);
    RWASSERT(cluster->data != NULL);

    if (!(cluster->flags & rxCLFLAGS_EXTERNAL))
    {
        P2CHECKHEAPPTR(cluster->data);
        RxHeapFree(_rxHeapGlobal, cluster->data);
    }

    cluster->data = NULL;

    cluster->numAlloced = 0;
    cluster->numUsed = 0;

    /* No valid data */
    cluster->flags = rxCLFLAGS_NULL;

    RWRETURN((RxCluster *)NULL);
}

#if (defined(DOXYGEN) || defined(RWDEBUG))

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterLockRead
 * locks a cluster for reading in an \ref RxPacket.
 *
 * RxClusterLockRead will fail (indicated by a NULL return) if the packet
 * does not contain the requested cluster. To ensure that this does not
 * happen, the node's \ref RxNodeDefinition should flag the cluster as
 * required [rxCLREQ_REQUIRED or rxCLREQ_DONTWANT - the latter means that
 * the cluster is required but any prior data it contains is not wanted].
 * Even if this is done, the cluster may not be present if no subsequent
 * nodes in the pipeline require it and no prior nodes create it. The node
 * can ensure that it definitely is there by specifying rxCLFORCEPRESENT
 * for the forcePresent member of the cluster's \ref RxClusterRef in the
 * clustersOfInterest array in the \ref RxNodeDefinition. This is important
 * if the cluster is to be dispatched in the packet to another pipeline.
 *
 * It is possible for an rxCLREQ_REQUIRED cluster to be empty, and to
 * guard against this nodes should test (cluster->numUsed > 0).
 * rxCLREQ_OPTIONAL clusters may not be present and should always be
 * tested for presence.
 *
 * Clusters locked for reading may not be resized, and modification of
 * their data is not permitted.
 *
 * Note that this function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * \param  packet   A pointer to the packet containing the cluster
 * \param  clusterIndex   An \ref RwUInt32 equal to the cluster's index
 *                        within the node's
 *                        RxNodeDefinition.io.clustersOfInterest array
 *
 * Self-contained example
 * that iterates through a packet's ObjVerts cluster.
 * \verbatim
   RwBool
   ProcessPacket(RxPacket *pk)
   {
           RxCluster *clObjVerts;

           // cluster we wish to LockRead() is identified by its index
           // within the node's RxNodeDefinition.io.clustersOfInterest array

           clObjVerts = RxClusterLockRead(pk, 3);
           if ( (clObjVerts != NULL ) && (clObjVerts->numUsed > 0))
           {
                   RwUInt32 n;

                   for (n = 0; n < clObjVerts->numUsed; n++) // iterate over ObjVerts array
                   {
                           const RwObjSpace3DVertex *objVert =
                               RxClusterGetCursorData(clObjVerts, const RwObjSpace3DVertex);

                           ... // do something with objVert

                           RxClusterIncCursor(clObjVerts);
                   }

                   RxClusterUnlock(clObjVerts);
           }
   }
   \endverbatim
 *
 * \return A pointer to the cluster if successful, NULL if there is an error
 *
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxPacketFetch
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 */
RxCluster          *
RxClusterLockRead(RxPacket * packet, RwUInt32 clusterIndex)
{
    RwUInt32            slot = packet->inputToClusterSlot[clusterIndex];

    RWAPIFUNCTION(RWSTRING("RxClusterLockRead"));

    if (slot != (RwUInt32) - 1)
    {
        RxCluster          *cluster = &packet->clusters[slot];

        /*if (cluster->flags & rxCLFLAGS_CLUSTERVALID)
         * { */
        RxClusterResetCursor(cluster);

        RWRETURN(cluster);
        /*} */
    }

    RWRETURN((RxCluster *) NULL);
}
#endif /* (defined(DOXYGEN) || defined(RWDEBUG)) */

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterLockWrite
 * locks a cluster for writing.
 *
 * Returns NULL if the packet does not contain the requested cluster. [The
 * node's \ref RxNodeDefinition record may list a cluster as rxCLVALID_VALID,
 * but if dependency chasing identifies that no subsequent nodes have any
 * interest in the cluster and no prior nodes create the cluster, it will
 * be eliminated: packets will not make provision for the cluster, and it
 * will not be possible to create it. To override this elimination behavior,
 * set the RxClusterRef.forcePresent flag to rxCLFORCEPRESENT - see also
 * \ref RxClusterLockRead]
 *
 * Clusters locked for writing may have their data initialized, resized,
 * destroyed or modified. This function sets the rxCLFLAGS_MODIFIED bit in
 * the cluster's flags so that it can later be determined that the cluster's
 * data has been edited.
 *
 * If \ref RxClusterLockWrite is called on an external (by reference) cluster,
 * "internalization" occurs - the cluster data is copied to permit
 * modification, and the cluster becomes internal (dropping the reference
 * altogether). It is this internal cluster that will subsequently be
 * propagated. For a more detailed discussion of the internal/external
 * distinction, see \ref RxClusterSetExternalData or \ref RxClusterSetData.
 *
 * \param  packet  A pointer to the packet containing the cluster
 * \param  clusterIndex   An \ref RwUInt32 equal to the cluster's index
 * within the node's RxNodeDefinition.io.clustersOfInterest array
 * \param  node   A pointer to the calling pipeline node
 *
   Creating a cluster
 * \verbatim
    RwBool
    ProcessRenderState( RxPipelineNodeInstance *self,
                        const RxPipelineNodeParam *params )
    {
        RxPacket            *packet;

        RxCluster           *clRenderState;

        RxRenderStateVector *renderStateData;


        packet = (RxPacket *)RxPacketFetch( self );
        RWASSERT( NULL != packet );

        // cluster we wish to LockWrite() is identified by its index
        // within the node's RxNodeDefinition.io.clustersOfInterest array

        clRenderState = RxClusterLockWrite( packet, 1, self );
        RWASSERT( clRenderState != NULL );

        clRenderState = RxClusterInitializeData( clRenderState, 1, sizeof(RxRenderStateVector) );
        RWASSERT( NULL != clRenderState );

        renderStateData = RxClusterGetCursorData( clRenderState, RxRenderStateVector );
        RWASSERT( NULL != renderStateData );

        memset( renderStateData, 0, sizeof(RxRenderStateVector) );

        // ... and any further changes to renderStateData

        RxClusterUnlock( clRenderState );

        // ...
    }
   \endverbatim
 *
 * \return A pointer to the cluster on success, or NULL if there is an error
 *
 * \see RxClusterLockRead
 * \see RxClusterUnlock
 * \see RxClusterInitializeData
 * \see RxClusterDestroyData
 * \see RxClusterResizeData
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 * \see RxPacketFetch
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 */
RxCluster          *
RxClusterLockWrite(RxPacket * packet,
                   RwUInt32 clusterIndex,
                   RxPipelineNode * node __RWUNUSED__)
{
    RwUInt32            slot = packet->inputToClusterSlot[clusterIndex];

    RWAPIFUNCTION("RxClusterLockWrite");

    RWASSERT(RxPipelineInstanced);

    /* management of RxCluster.clusterRef:
     * (o) ... is set from node's SlotClusterRefs on packetcreate()/packetbetweenpipelines().
     * (o) ... is NULLed when cluster terminated.
     * (o) ... when ClusterLockWrite() is called, if NULL, then we're witnessing the birth
     * of a new cluster, so ClusterRef set correspondingly.
     *
     * similar logic applies to setting of cluster attributes.
     *
     * one consequence is that cluster attributes are "undefined" (in C jargon)
     * prior to RwClusterLock[Read/Write], which requires no special considerations
     * from the node perspective as they have to call RwClusterLock[Read/Write]
     * in order to get an RxCluster *.
     */

    /* No error message - returning NULL is fine. In some cases the
     * node handles NULL ok, so we don't want piles of error messages */
    if (slot != (RwUInt32) - 1)
    {
        RxCluster          *cluster = &packet->clusters[slot];

        if (cluster->clusterRef == NULL)
        {
            RxPipelineCluster  *pipelineClusterRef =
                packet->slotClusterRefs[slot];

            RWASSERT(pipelineClusterRef != NULL);

            /* as above, "birth of a new cluster" */

            cluster->clusterRef = pipelineClusterRef;
            cluster->attributes = pipelineClusterRef->creationAttributes;
        }

        /* The cluster HAS been modified */
        cluster->flags |= rxCLFLAGS_MODIFIED;

        /*if (cluster->flags & rxCLFLAGS_CLUSTERVALID)
         * { */
        if ((cluster->flags & rxCLFLAGS_EXTERNALMODIFIABLE) ==
            rxCLFLAGS_EXTERNAL)
        {
            /* copy rxCLFLAGS_EXTERNAL cluster to create a non-rxCLFLAGS_EXTERNAL
             * cluster, which we will subsequently propagate
             *
             * note that if rxCLFLAGS_EXTERNALMODIFIABLE, we don't do a copy,
             * but simply return the data pointer
             *
             * this means that if you LockWrite() a rxCLFLAGS_EXTERNAL cluster,
             * you will be able to resize/free afterwards (coz of the copy
             * to non-rxCLFLAGS_EXTERNAL)
             *
             * BUT: if you LockWrite() a rxCLFLAGS_EXTERNALMODIFIABLE cluster,
             * subsequent attempts to resize/free will fail
             */
            void               *p;
            RwUInt32            numAlloced;
            RwUInt32            numUsed;

            p = cluster->data;
            numAlloced = cluster->numAlloced;
            numUsed = cluster->numUsed;

            cluster->flags &= ~rxCLFLAGS_EXTERNAL;
            cluster->data = NULL;

            if (RxClusterInitializeData
                (cluster, numAlloced, cluster->stride) == NULL)
            {
                cluster->flags |= rxCLFLAGS_EXTERNAL;
                cluster->data = p;
                RWRETURN((RxCluster *)NULL);
            }

            cluster->numUsed = numUsed;

            memcpy(cluster->data, p, numUsed * cluster->stride);
        }

        RxClusterResetCursor(cluster);

        RWRETURN(cluster);
        /*} */
    }

    RWRETURN((RxCluster *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterUnlock
 * unlocks a cluster.
 *
 * Counterpart to \ref RxClusterLockRead and \ref RxClusterLockWrite ;
 * call when the \ref RxCluster
 * is no longer required  - when reading, writing or resizing is
 * complete.
 *
 * \param cluster  A pointer to the cluster to unlock
 *
 * \return None
 *
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
void
RxClusterUnlock(RxCluster * cluster __RWUNUSED__)
{
    RWAPIFUNCTION(RWSTRING("RxClusterUnlock"));

    RWRETURNVOID();
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeSendConfigMsg
 * requests information on node requirements.
 *
 * In the final stages of pipeline Unlock()-ing, (i) the pipelinenodeinit
 * method of each node in the pipeline is called [in bottom to top (consumer
 * -> producer) order]; (ii) the pipelinenodeconfig method of each node is
 * called, again in bottom to top order.
 *
 * It is expected that nodes will use the pipelinenodeinit method to
 * initialize their private data. Once the pipelinenodeinit phase is complete,
 * it is assumed that all nodes are initialized as necessary. Nodes may then
 * use the pipelinenodeconfig method as an opportunity to communicate with
 * other nodes in the pipeline and further ascertain requirements.
 *
 * A typical node's pipelinenodeconfig method might use
 * \ref RxPipelineNodeForAllConnectedOutputs (perhaps recursively) to identify
 * the nodes to which it outputs, and then use \ref RxPipelineNodeSendConfigMsg
 * to request information on their requirements.
 *
 * Nodes receive messages via their configmsghandler method. The return value
 * of \ref RxPipelineNodeSendConfigMsg is the node's response, or zero if the
 * message is unserviced.
 *
 * No default msg values are currently defined.
 *
 * The use of this function (and the related callback types,
 * \ref RxPipelineNodeConfigFn and \ref RxConfigMsgHandlerFn) is no longer
 * recommended. It may be removed from the library in subsequent versions.
 *
 * \param dest      Destination node for the message
 * \param msg       Message ID
 * \param intparam  Meaning is message-specific
 * \param ptrparam  Meaning is message-specific
 *
 * \return 0: unserviced; -ve: error; +ve: informative success
 *
 * \see RxPipelineNodeForAllConnectedOutputs
 * \see RxPipelineNodeGetInitData
 * \see RxLockedPipeUnlock
 */
RwUInt32
RxPipelineNodeSendConfigMsg(RxPipelineNode * dest,
                            RwUInt32 msg,
                            RwUInt32 intparam, void *ptrparam)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeSendConfigMsg"));

    RWASSERT(dest != NULL);

    if (dest != NULL)
    {
        RxConfigMsgHandlerFn configmsghandler =
            dest->nodeDef->nodeMethods.configMsgHandler;

        if (configmsghandler != NULL)
        {
            RwUInt32            result;

            result = configmsghandler(dest, msg, intparam, ptrparam);

            /* 0: unserviced;
             * -ve: error;
             * +ve: informative success
             */
            RWRETURN(result);
        }
        else
        {
            RWRETURN(0);       /* message unserviced */
        }
    }

    RWRETURN((RwUInt32) - 1);  /* error: dest == NULL */
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeForAllConnectedOutputs
 * enumerates over the connected outputs of a node.
 *
 * Enumeration function. The callback is executed on every connected output
 * of the specified node. The callback may return NULL to terminate
 * enumeration, or a non-NULL value to continue.
 *
 * Expected use is in pipelinenodeconfig methods.
 *
 * Callback prototype:
 *
 * \verbatim
   typedef RxPipelineNode *
   (*RxPipelineNodeOutputCallBack)(RxPipelineNode *node,
                                   RxPipelineNode *outputnode,
                                   void *callbackdata);
   \endverbatim
 *
 * \param node  A pointer to the pipeline node
 * \param pipeline  A pointer to pipeline node's containing pipeline
 * \param callbackfn  Per-connected-output callback function
 * \param callbackdata  A pointer to user data for enumeration - passed to the callback function
 *
 * \return A pointer to the specified pipeline node on success, or NULL if there is an error
 *
 * \see RxPipelineNodeSendConfigMsg
 */
RxPipelineNode     *
RxPipelineNodeForAllConnectedOutputs(RxPipelineNode * node,
                                     RxPipeline * pipeline,
                                     RxPipelineNodeOutputCallBack
                                     callbackfn, void *callbackdata)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeForAllConnectedOutputs"));

    RWASSERT(node != NULL);
    RWASSERT(pipeline != NULL);
    /* node within pipeline...? */
    RWASSERT((node >= &pipeline->nodes[0]) &&
             (node < &pipeline->nodes[pipeline->numNodes]));
    RWASSERT(callbackfn != NULL);

    if ((node != NULL) &&
        (pipeline != NULL) &&
        (node >= &pipeline->nodes[0]) &&
        (node < &pipeline->nodes[pipeline->numNodes]) &&
        (callbackfn != NULL))
    {
        RwUInt32            n;

        for (n = 0; n < node->numOutputs; n++)
        {
            RwUInt32            outputindex = node->outputs[n];

            /* unconnected outputs are signified by
             * node index == (RwUInt32) -1 [see _NodeCreate()] */
            if (outputindex < pipeline->numNodes)
            {
                if (callbackfn(node,
                               &pipeline->nodes[outputindex],
                               callbackdata) == NULL /* enumeration early out */
                    )
                {
                    break;
                }
            }
        }

        RWRETURN(node);
    }

    RWRETURN((RxPipelineNode *)NULL);
}

void
_rwPipelineCheckForTramplingOfNodePrivateSpace(RxPipeline * pipeline)
{
    RWFUNCTION(RWSTRING("_rwPipelineCheckForTramplingOfNodePrivateSpace"));

    RWASSERT(pipeline != NULL);

    if (pipeline != NULL)
    {
        RwUInt32 n;

        for (n = 0;n < pipeline->numNodes;n++)
        {
            RxPipelineNode *node = &pipeline->nodes[n];

            /* okay, let's see what this node's up to with its private space */
            if (node->privateData != NULL)
            {
                RwUInt32 privateDataSize;
                RwUInt32 readMagic;
                RwBool   tramplingOfNodePrivateSpace = FALSE;

                privateDataSize = node->nodeDef->pipelineNodePrivateDataSize;

                /* try and read back magic written in p2dep.c, _ForAllNodesWriteClusterAllocations() */
                readMagic = *(RwUInt32 *)
                    (((RwUInt8 *)node->privateData) +
                     ((privateDataSize + 3U) & ~3U));

                if (readMagic != RXNODEPRIVATESPACEMAGIC)
                {
                    tramplingOfNodePrivateSpace = TRUE;
                }

                RWASSERT(!tramplingOfNodePrivateSpace);
            }
        }
    }

    RWRETURNVOID();
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeGetPipelineCluster
 * gets a pipeline cluster from a pipeline node.
 *
 * Each pipeline maintains a collection of \ref RxPipelineCluster records,
 * one for each cluster used by the pipeline.
 *
 * An \ref RxPipelineCluster record holds a creationAttributes value - when
 * a cluster is created in the pipeline (\ref RxClusterInitializeData),
 * the cluster's attributes field is initialized from
 * RxPipelineCluster.creationAttributes.
 *
 * \ref RxPipelineNodeGetPipelineCluster is most commonly used in a node's
 * pipelinenodeinit method, as a precursor to modifying a cluster's
 * creationAttributes.
 *
 * \param node  A pointer to the pipeline node
 * \param clustersOfInterestIndex  An \ref RwUInt32 equal to the cluster's
 * index within the node's RxNodeDefinition.io.clustersOfInterest array
 *
 * \return A pointer to the pipeline cluster on success, or NULL if there is
 * an error
 *
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineClusterSetCreationAttributes
 * \see RxClusterGetAttributes
 * \see RxClusterSetAttributes
 */

RxPipelineCluster  *
RxPipelineNodeGetPipelineCluster(RxPipelineNode * node,
                                 RwUInt32 clustersOfInterestIndex)
{
    RxPipelineCluster  *result = (RxPipelineCluster *)NULL; /* failure */

    RWAPIFUNCTION(RWSTRING("RxPipelineNodeGetPipelineCluster"));

    /* validate parameters */

    RWASSERT(node != NULL);
    RWASSERT(clustersOfInterestIndex <
             node->nodeDef->io.numClustersOfInterest);

    if (node != NULL &&
        clustersOfInterestIndex <
        node->nodeDef->io.numClustersOfInterest)
    {
        RwUInt32            packetSlotIndex =

            node->inputToClusterSlot[clustersOfInterestIndex];

        if (packetSlotIndex != ((RwUInt32) - 1))
        {
            result = node->slotClusterRefs[packetSlotIndex];

            RWASSERT(result != NULL); /* should have succeeded */
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineClusterGetCreationAttributes
 * gets creation attributes from a pipeline cluster.
 *
 * Each pipeline maintains a collection of \ref RxPipelineCluster records,
 * one for each cluster used by the pipeline.
 *
 * An \ref RxPipelineCluster record holds a creationAttributes value - when
 * a cluster is created in the pipeline (\ref RxClusterInitializeData), the
 * cluster's attributes field is initialized from
 * RxPipelineCluster.creationAttributes. Nodes may subsequently modify the
 * cluster's attributes as the packet flows down the pipe.
 *
 * Interpretation of the cluster attributes field is with reference to the
 * cluster's attributeSet identifier. For example, PlayStation 2 specific
 * pipelines tend to employ clusters with the attributeSet identifier "PS2";
 * given the attributeSet identifier "PS2", we know that the attribute values
 * are constructed from the PlayStation 2 cluster attributes (CL_ATTRIB_OPAQUE,
 * CL_V4_16, etc.).
 *
 * It is good practice for a node's pipelinenodeinit/pipelinenodeconfig
 * methods to test a cluster's attributeSet identifier before reading or
 * writing attribute values (e.g.
 * RxPipelineClusterAssertAttributeSet(cl, RWSTRING("PS2"))).
 *
 * \param cluster  A pointer to the pipeline cluster to
 * retrieve creation attributes from
 *
 * \return The cluster's attributes, which may equal zero, or -1 if there is an error
 *
 * \see RxPipelineClusterSetCreationAttributes
 * \see RxPipelineNodeGetPipelineCluster
 * \see RxClusterGetAttributes
 * \see RxClusterSetAttributes
 */

RwUInt32
RxPipelineClusterGetCreationAttributes(RxPipelineCluster * cluster)
{
    RwUInt32            result = (RwUInt32) - 1; /* failure */

    RWAPIFUNCTION(RWSTRING("RxPipelineClusterGetCreationAttributes"));

    RWASSERT(cluster != NULL);

    if (cluster != NULL)
    {
        result = cluster->creationAttributes; /* ... a result */
    }

    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineClusterSetCreationAttributes
 *  sets creation attributes.
 *
 * Each pipeline maintains a collection of \ref RxPipelineCluster records,
 * one for each cluster used by the pipeline.
 *
 * An \ref RxPipelineCluster record holds a creationAttributes value - when
 * a cluster is created in the pipeline (\ref RxClusterInitializeData), the
 * cluster's attributes field is initialized from
 * RxPipelineCluster.creationAttributes. nodes may subsequently modify the
 * cluster's attributes as the packet flows down the pipe.
 *
 * Interpretation of the cluster attributes field is with reference to the
 * cluster's attributeSet identifier. For example, PlayStation 2 specific pipelines
 * tend to employ clusters with the attributeSet identifier "PS2"; given
 * the attributeSet identifier "PS2", we know that the attribute values are
 * constructed from the PlayStation 2 cluster attributes (CL_ATTRIB_OPAQUE, CL_V4_16,
 * etc.).
 *
 * It is good practice for a node's pipelinenodeinit/pipelinenodeconfig
 * methods to test a cluster's attributeSet identifier before reading or
 * writing attribute values (e.g.
 * RxPipelineClusterAssertAttributeSet(cl, RWSTRING("PS2"))).
 *
 * \param cluster  The pipeline cluster to set creation attributes of.
 * \param creationAttributes  New creation attributes value
 *
 * \return A pointer to the modified pipeline cluster on success, or NULL if there is an error
 *
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineNodeGetPipelineCluster
 * \see RxClusterGetAttributes
 * \see RxClusterSetAttributes
 */

RxPipelineCluster  *
RxPipelineClusterSetCreationAttributes(RxPipelineCluster * cluster,
                                       RwUInt32 creationAttributes)
{
    RxPipelineCluster  *result = (RxPipelineCluster *)NULL; /* failure */

    RWAPIFUNCTION(RWSTRING("RxPipelineClusterSetCreationAttributes"));

    RWASSERT(cluster != NULL);

    if (cluster != NULL)
    {
        cluster->creationAttributes = creationAttributes;

        result = cluster;      /* success */
    }

    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterGetAttributes
 * gets the attributes of a cluster.
 *
 * Each \ref RxCluster
 * in each \ref RxPacket
 * has an attributes field.
 * \ref RxClusterInitializeData initializes the attributes field to a
 * creationAttributes value specific to the cluster and the pipeline
 * in which creation occurs. Using RxClusterGetAttributes or
 * \ref RxClusterSetAttributes, this attributes value may be read and
 * modified as the packet flows down the pipeline.
 *
 * Interpretation of the cluster attributes field is with reference to the
 * cluster's attributeSet identifier. For example, PlayStation 2 specific pipelines
 * tend to employ clusters with the attributeSet identifier "PS2"; given
 * the attributeSet identifier "PS2", we know that the attribute values are
 * constructed from the PlayStation 2 cluster attributes (CL_ATTRIB_OPAQUE, CL_V4_16,
 * etc.).
 *
 * It is good practice for a node's pipelinenodeinit/pipelinenodeconfig
 * methods to test a cluster's attributeSet identifier before reading or
 * writing attribute values (e.g.
 * RxPipelineClusterAssertAttributeSet(cl, RWSTRING("PS2"))).
 *
 * \param cluster  A pointer to the cluster from which to retrieve the
 * attributes
 *
 * \return An \ref RwUInt32 equal to the attributes of the cluster if
 * successful or -1 if there is an error
 *
 * \see RxClusterSetAttributes
 * \see RxPipelineNodeGetPipelineCluster
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineClusterSetCreationAttributes
 */
RwUInt32
RxClusterGetAttributes(RxCluster * cluster)
{
    RwUInt32            result = (RwUInt32) - 1; /* failure */

    RWAPIFUNCTION(RWSTRING("RxClusterGetAttributes"));

    RWASSERT(cluster != NULL);

    if (cluster != NULL)
    {
        result = cluster->attributes; /* ... a result */
    }

    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterSetAttributes
 * sets the attributes of a cluster.
 *
 * Each \ref RxCluster
 * in each \ref RxPacket
 * has an attributes field.
 * \ref RxClusterInitializeData initializes the attributes field to a
 * creationAttributes value specific to the cluster and the pipeline
 * in which creation occurs. Using the \ref RxClusterGetAttributes and
 * \ref RxClusterSetAttributes functions, this attributes value may be
 * read and modified as the packet flows down the pipeline.
 *
 * Interpretation of the cluster attributes field is with reference to the
 * cluster's attributeSet identifier. For example, PlayStation 2 specific
 * pipelines tend to employ clusters with the attributeSet identifier "PS2";
 * given the attributeSet identifier "PS2", we know that the attribute values
 * are constructed from the PlayStation 2 cluster attributes (CL_ATTRIB_OPAQUE,
 * CL_V4_16, etc.).
 *
 * It is good practice for a node's pipelinenodeinit/pipelinenodeconfig
 * methods to test a cluster's attributeSet identifier before reading or
 * writing attribute values (e.g.
 * RxPipelineClusterAssertAttributeSet(cl, RWSTRING("PS2"))).
 *
 * \param cluster  A pointer to the cluster whose attributes are to be set
 * \param attributes  An \ref RwUInt32 equal to the new attributes value
 *
 * \return A pointer to the modified cluster, or NULL if there is an error
 *
 * \see RxPipelineNodeGetPipelineCluster
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineClusterSetCreationAttributes
 */

RxCluster          *
RxClusterSetAttributes(RxCluster * cluster, RwUInt32 attributes)
{
    RxCluster          *result = (RxCluster *)NULL; /* failure */

    RWAPIFUNCTION(RWSTRING("RxClusterSetAttributes"));

    RWASSERT(cluster != NULL);

    if (cluster != NULL)
    {
        cluster->attributes = attributes;

        result = cluster;      /* success */
    }

    RWRETURN(result);
}

#if ( ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) || defined(EXECUTENODEFUNCTION) ) )

static void
ExecuteNode(RxPipeline * _pipeline,
            RxPipelineNode * _node,
            RxPipelineNodeParam * _params)
{
    RWFUNCTION(RWSTRING("ExecuteNode"));

    _rxExecuteNodeMacro(_pipeline, _node, _params);

    RWRETURNVOID();
}

#else /* ( ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) || defined(EXECUTENODEFUNCTION) ) ) */

#define ExecuteNode(_pipeline, _node, _params) \
    _rxExecuteNodeMacro(_pipeline, _node, _params)

#endif /* ( ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) || defined(EXECUTENODEFUNCTION) ) ) */

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineExecute
 * executes a pipeline.
 *
 * The \ref RxPipeline object describes a directed acylic graph of nodes,
 * which must be in an unlocked state for execution [\ref RxPipelineLock
 * and \ref RxLockedPipeUnlock calls bracket edits of the DAG; inside the
 * \ref RxLockedPipeUnlock call, analysis is made of data flow through the
 * pipeline, which is then used to optimize \ref RxPipelineExecute
 * performance].
 *
 * Execution begins at the head node of the pipeline, proceeding downwards
 * through the flattened ("topologically sorted") pipeline. Parameter "data"
 * is passed into each node. Typically, a node near the top of the pipeline
 * will process the object referenced by this data pointer to generate
 * one or more \ref RxPacket's,
 * which are then directed down the graph for further processing.
 *
 * \ref RxPipelineNode's may invoke other pipelines by dispatching \ref RxPacket s
 * with the inter-DAG \ref RxPacketDispatchToPipeline function rather than the
 * \ref RxPacketDispatch intra-DAG function [which outputs the \ref RxPacket to
 * the node connected to output "outputIndex"].
 *
 * Pipeline execution proceeds in a nested manner such that inside a dispatch,
 * the \ref RxPacket in fact proceeds along the rest of the pipeline and is
 * destroyed explicitly by a node or automatically on dispatch to an
 * unconnected node output or on exit of the terminal node. Hence, a dispatch
 * is equivalent to calling \ref RxPacketDestroy from the point of view of the
 * calling node - the packet can no longer be used within that node and
 * another must be created. \ref RxPacketDispatchToPipeline and
 * \ref RxPacketDispatch are the only means of causing subsequent nodes to
 * execute (i.e pipeline execution to continue).
 *
 * Nodes executing within a pipeline should use the \ref RxHeap structure
 * (as detailed in \ref RxHeapGetGlobalHeap and \ref RxHeapCreate) to allocate
 * "transient" memory, where "transient" means that the memory can safely be
 * lost as soon as the pipeline execution terminates or sooner (the heap is
 * cleared with a call to \ref RxHeapReset before execution begins, unless the
 * heapReset parameter to \ref RxPipelineExecute is FALSE, as used by
 * \ref RwIm3DTransform for example). Allocations of cluster memory, for
 * instance (through \ref RxClusterInitializeData and \ref RxClusterResizeData),
 * use the pipeline execution heap.
 *
 * The heap attached to the current pipeline execution is accessible via
 * the \ref RxPipelineNodeParam parameter of pipeline node \ref RxNodeBodyFn's,
 * with the aid of \ref RxPipelineNodeParamGetHeap. While there is currently
 * one global heap, the system may in the future be extended to use different
 * heaps for each pipeline execution, such that many pipelines could be
 * executed simultaneously on multiprocessor systems.
 *
 * Early termination: the nested execution model descibed above means that
 * terminating pipeline execution is achieved merely by not dispatching to
 * a subsequent node or pipeline. However, there is no guarantee that nodes
 * above the current node will not create and dispatch further packets. So,
 * if a node returns FALSE (signifying an error) then pipeline execution will
 * terminate as soon as all nodes above it can exit and no further dispatches
 * will be allowed.
 *
 * If the pipeline (or any invoked pipelines) terminates with an error,
 * RxPipelineExecute will return NULL.
 *
 * \param pipeline  A pointer to the pipeline to execute
 * \param data  A pointer to data to pass to node body
 * methods (the purpose is pipeline-specific)
 * \param heapReset  TRUE in normal usage; FALSE to carry over heap
 * contents from the previous pipeline execution, i.e not
 * call \ref RxHeapReset before executing
 *
 * Invoke the default atomic pipeline on "atomic". All things being equal, the atomic
 * pipeline will generate atomic->numMaterialsUsed packets, each packet corresponding to
 * one RwMaterial, and containing the subset of the atomic's geometry that specifies that material.
 * These packets will then be scattered to material pipelines (see RpMaterialSetPipeline,
 * RpMaterialSetDefaultPipeline) for transform, lighting and rendering.
 *
 * \verbatim
   if ( !RxPipelineExecute(defaultAtomicPipeline, (void *) atomic, TRUE) )
   {
           // pipeline execution incurred an error
   }
   \endverbatim
 *
 * \return A pointer to the \ref RxPipeline executed if it runs to completion
 * without error, otherwise NULL
 *
 * \see RxPipelineCreate
 * \see RxPipelineDestroy
 * \see RxPipelineLock
 * \see RxLockedPipeUnlock
 * \see RxLockedPipeAddFragment
 */
RxPipeline         *
RxPipelineExecute(RxPipeline * pipeline, void *data, RwBool heapReset)
{
    RxPipelineNode     *node;

    RWAPIFUNCTION(RWSTRING("RxPipelineExecute"));
    RWASSERT(RxPipelineInstanced);
    RWASSERT(NULL != pipeline);

    /* We're not threadsafe! */
    RWASSERT(_rxExecCtxGlobal.pipeline == NULL);

    if (FALSE != heapReset)
    {
        RxHeapReset(_rxHeapGlobal);
    }

    _rxExecCtxGlobal.exitCode = TRUE;
    _rxExecCtxGlobal.pipeline = pipeline;
    _rxExecCtxGlobal.params.dataParam = data;
    /* Yay! This can now be changed per pipeline execute!
     * The vital step in enabling multithreadable PowerPipe */
    _rxExecCtxGlobal.params.heap = RxHeapGetGlobalHeap();
    RWASSERT(NULL != _rxExecCtxGlobal.params.heap);

    /* Check the pipeline isn't being executed already */
    RWASSERT(pipeline->embeddedPacketState <= rxPKST_UNUSED);
    /* OI!! TODO: You should check the pipeline to see if it
     * has any (non-optional) input requirements - if so
     * output "Pipeline has non-optional input requirements,
     * it can only be called from another pipeline"
     * Now that runto/runfrom are gone, this is OK */

    /* No packet created yet */
    pipeline->embeddedPacketState = rxPKST_PACKETLESS;

    node = &pipeline->nodes[0];

    ExecuteNode(pipeline, node, &(_rxExecCtxGlobal.params));

    _rxExecCtxGlobal.pipeline = (RxPipeline *)NULL;
    _rxExecCtxGlobal.params.dataParam = NULL;
    _rxExecCtxGlobal.params.heap = (RxHeap *)NULL;

    if (FALSE != _rxExecCtxGlobal.exitCode)
    {
        RWRETURN(pipeline);
    }

    RWRETURN((RxPipeline *)NULL);
}


/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineCreate
 * creates an \ref RxPipeline.
 *
 * \ref RxPipelineCreate may return NULL if there is insufficient memory.
 *
 * The following code creates a worldsector object pipeline containing
 * the three library nodes WorldSectorInstance.csl,
 * WorldSectorEnumerateLights.csl and MaterialScatter.csl.
 *
 * \verbatim
   RxPipeline *
   CreateWorldSectorPipeline(void)
   {
           RxPipeline *pipe;
           RxLockedPipe *lpipe;

           pipe = RxPipelineCreate();
           if ( pipe != NULL )
           {
                   lpipe = RxPipelineLock(pipe);
                   if ( lpipe != NULL )
                   {
                           if ( RxLockedPipeAddFragment(lpipe, NULL,
                                                        RxNodeGetNodeWorldSectorInstance(),
                                                        RxNodeGetNodeWorldSectorEnumerateLights(),
                                                        RxNodeGetNodeMaterialScatter(),
                                                        NULL) )
                           {
                                   if ( RxLockedPipeUnlock(lpipe) )
                                   {
                                           return (pipe); // success
                                   }
                           }
                   }

                   // fall through to here for cleanup if pipeline was
                   // Create()d okay, but one of the construction calls
                   // [Lock(), AddFragment(), Unlock()] has failed

                   _rxPipelineDestroy(pipe);
           }

           return NULL; // failure
   }
   \endverbatim
 *
 * \return A pointer to the created pipeline on success, or NULL if there is an error
 *
 * \see RxPipelineDestroy
 * \see RxPipelineLock
 * \see RxLockedPipeUnlock
 * \see RxPipelineExecute
 */
RxPipeline         *
RxPipelineCreate(void)
{
    RxPipeline         *pipeline;

    RWAPIFUNCTION(RWSTRING("RxPipelineCreate"));

    RWASSERT(RxPipelineInstanced);
#if (defined(RWDEBUG))
    if (!RxPipelineInstanced)
    {
        RwDebugSendMessage(rwDEBUGERROR,
                           "RxPipelineCreate",
                           "You cannot create pipelines until after RenderWare Graphics has been started with RwEngineStart");
    }
#endif /* (defined(RWDEBUG)) */

    pipeline = (RxPipeline *)RwFreeListAlloc(RXPIPELINEGLOBAL(pipesFreeList),	
                                     rwID_PIPEMODULE | rwMEMHINTDUR_EVENT);

    if (pipeline != NULL)
    {
        /* If this memset is removed, it WILL be necessary
         * to zero the pipeline's fields explicitly. */
        memset(pipeline, 0x00U, sizeof(RxPipeline));

        /* We shouldn't assume FALSE is zero I guess (yeah we should) */
        pipeline->locked = FALSE;

        RWRETURN(pipeline);
    }
    RWERROR((E_RW_NOMEM, sizeof(RxPipeline)));

    RWRETURN((RxPipeline *) NULL);
}

void
_rxPipelineDestroy(RxPipeline * Pipeline)
{
    RWFUNCTION(RWSTRING("_rxPipelineDestroy"));
    RWASSERT(RxPipelineInstanced);

    if (Pipeline)
    {
        /*
         * If any PipelineNodeInstances reference PipelineNodes in
         * this pipeline or packets reference this pipeline then
         * they're not gonna be happy, but ach who cares...
         */

        /* Destroy all the Nodes */
        RwUInt32            numNodes, i;
        RxPipelineNode     *Node = Pipeline->nodes;

        /* We use numNodes because Pipeline->numNodes
         * is changed by PipelineNodeDestroy()! */
        numNodes = Pipeline->numNodes;
        for (i = 0; i < numNodes; i++)
        {
            /* Decrements pipeline->numNodes */
            PipelineNodeDestroy(Node, Pipeline);
            Node++;
        }
        Pipeline->nodes = (RxPipelineNode *)NULL;

        /* Finally destroy the pipeline */
        if (Pipeline->superBlock != NULL)
        {
            RWASSERT(RWCRTISVALIDHEAPPOINTER(Pipeline->superBlock));
            RwFree(Pipeline->superBlock);
            Pipeline->superBlock = (RwUInt32 *)NULL;
            Pipeline->superBlockSize = 0;
        }

        /* There should really be a freelist API function that tests for trampling */
        RwFreeListFree(RXPIPELINEGLOBAL(pipesFreeList), Pipeline);
        Pipeline = (RxPipeline *)NULL; /* Nerd. */
    }

    RWRETURNVOID();
}

#if (defined(DOXYGEN) || defined(RWDEBUG))

#if (defined(DOXYGEN))

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineDestroy
 * destroys a \ref RxPipeline,
 * freeing all associated resources.
 *
 * Counterpart to \ref RxPipelineCreate.
 *
 * \param  Pipeline   A pointer to the pipeline to destroy
 *
 * \return None
 *
 * \warning This is infact implemented as a macro
 *
 * \see RxPipelineCreate
 * \see RxPipelineLock
 * \see RxLockedPipeUnlock
 * \see RxPipelineExecute
 */
void
RxPipelineDestroy(RxPipeline * Pipeline)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineDestroy"));

    RWRETURNVOID();
}
#endif /* (defined(DOXYGEN)) */


/**
 * \ingroup rwcoregeneric
 * \ref RxPacketFetch
 * fetches the current packet if there is one,
 * returning a pointer to an \ref RxPacket
 * or NULL if there is no packet currently.
 *
 * The \ref RxPacketFetch -ed packet should be \ref RxPacketDispatchToPipeline -ed,
 * \ref RxPacketDispatch -ed or \ref RxPacketDestroy -ed. If a node fails to do so,
 * the packet will automatically be freed on exit of the node. Pipeline
 * execution proceeds in a nested manner such that inside a dispatch, the
 * \ref RxPacket in fact proceeds along the rest of the pipeline and is
 * destroyed explicitly by a node or automatically on dispatch to an
 * unconnected node output or on exit of the terminal node. Hence, a dispatch
 * is equivalent to calling \ref RxPacketDestroy from the point of view of the
 * current node - the packet can no longer be used within this node and
 * another must be created.
 *
 * The convention is to give terminal nodes an output to which
 * they dispatch, while recognizing that this output will usually remain
 * unconnected - dispatch to an unconnected output causes the packet to be
 * destroyed.
 *
 * \ref RxPacketFetch should only be called once within a node, given the
 * nested pipeline execution structure described above. \ref RxPacketDispatch
 * and \ref RxPacketDispatchToPipeline are the only mechanisms to progress
 * along a pipeline and they pass along at most one \ref RxPacket.
 *
 * \note This function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * \param Node  A pointer to the calling pipeline node
 *
 * \ref RxPacketFetch is used within node body methods. This example
 * shows the bones of a typical node.
 *
 * \verbatim
   {
           RxPacket *pk;

           pk = RxPacketFetch(self);
           RWASSERT (NULL != pk);

           ... // process packet

           // dispatch packet to output 0 (default)
           RxPacketDispatch(pk, 0, self);

           return(TRUE);
   }
   \endverbatim
 *
 * \return A pointer to the current packet or NULL if there isn't one
 *
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 * \see RxPacketCreate
 * \see RxPacketDestroy
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
RxPacket           *
RxPacketFetch(RxPipelineNode * Node __RWUNUSED__)
{
    RWAPIFUNCTION(RWSTRING("RxPacketFetch"));

    /* Has a packet been dispatched to the current node? */
    if ((_rxExecCtxGlobal.pipeline)->embeddedPacketState == rxPKST_PENDING)
    {
        (_rxExecCtxGlobal.pipeline)->embeddedPacketState = rxPKST_INUSE;
        RWRETURN((_rxExecCtxGlobal.pipeline)->embeddedPacket);
    }

    /* The packet should be in the PACKETLESS or UNUSED states atm */
    RWASSERT((_rxExecCtxGlobal.pipeline)->embeddedPacketState != rxPKST_INUSE);

    /* There is no packet to fetch! */
    RWRETURN((RxPacket *)NULL);
}

#endif /* (defined(DOXYGEN) || defined(RWDEBUG)) */

#if (defined(DOXYGEN))

/**
 * \ingroup rwcoregeneric
 * \ref RxPacketDestroy
 * destroys a packet.
 *
 * Data for all valid clusters is also freed, excepting "external"
 * [by reference] clusters. \ref RxPacketCreate must be called in
 * order to do further \ref RxPacket
 * processing in the current node.
 *
 * \param Packet  A pointer to the packet to destroy
 *
 * RxPacketDestroy is used within node body methods. This example
 * shows the bones of an atypical node.
 *
 * \verbatim
   {
           RxPacket *pk;

           pk = RxPacketFetch(self);
           RWASSERT (NULL != pk);

           ... // process packet

           RxPacketDestroy(pk, self);

           return(TRUE);
   }
   \endverbatim
 *
 * \return None
 *
 * \see RxPacketCreate
 * \see RxPacketFetch
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
void
RxPacketDestroy(RxPacket * Packet)
{
    RWAPIFUNCTION(RWSTRING("RxPacketDestroy"));

    RWRETURNVOID();
}

#endif /* (defined(DOXYGEN)) */

#if (defined(DOXYGEN) || defined(RWDEBUG))

/**
 * \ingroup rwcoregeneric
 * \ref RxPacketDispatch
 * dispatches an \ref RxPacketFetch -ed
 * or \ref RxPacketCreate -ed packet to
 *  an output of the current node.
 *
 * \ref RxPacketDispatch dispatches packets to one of the outputs of the
 * current node [\ref RxPacketDispatchToPipeline is used to dispatch packets
 * to another pipeline].
 *
 * If a packet is dispatched to an unconnected output, it will be destroyed.
 * NOTE, however, that this happens anyway from the point of view of the
 * calling node due to the nested nature of pipeline execution - when
 * \ref RxPacketDispatch is called, the packet is sent down the rest of
 * the pipeline within this call and will have been destroyed [as described
 * in \ref RxPacketFetch] by the time the dispatch function returns (you can
 * no longer operate on this packet, you will need to \ref RxPacketCreate
 * another).
 *
 * If the current node has not \ref RxPacketCreate -ed or \ref RxPacketFetch -ed a
 * packet then it is valid to pass NULL as the packet parameter to this
 * function (if a packet exists and you simply haven't checked, it will be
 * passed on anyway).
 *
 * As noted in the documentation for \ref RxPacketFetch, you should not
 * \ref RxPacketCreate or \ref RxPacketFetch a packet unless you subsequently
 * \ref RxPacketDispatch, \ref RxPacketDispatchToPipeline or \ref RxPacketDestroy
 * it.
 *
 * \note This function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * \param packet  A pointer to the packet to dispatch
 * \param output  RwInt32, the number of the output to dispatch the packet to
 * \param self    A pointer to the calling \ref RxPipelineNode
 *
 * RxPacketFetch is used within node body methods. This example
 * shows the bones of a typical node.
 *
 * \verbatim
   {
           RxPacket *pk;

           pk = RxPacketFetch(self);
           RWASSERT (NULL != pk);

           ... // process packet

           // dispatch packet to output 0 (default)
           RxPacketDispatch(pk, 0, self);

           return(TRUE);
   }
   \endverbatim
 *
 * \return None
 *
 * \see RxPacketDispatchToPipeline
 * \see RxPacketFetch
 * \see RxPacketCreate
 * \see RxPacketDestroy
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
void
RxPacketDispatch(RxPacket * packet __RWUNUSED__,
                 RwUInt32 output, RxPipelineNode * self)
{
    RWAPIFUNCTION(RWSTRING("RxPacketDispatch"));

    rxPacketDispatchMacro(packet, output, self);

    RWRETURNVOID();
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPacketDispatchToPipeline
 * dispatches a \ref RxPacketFetch -ed
 * or \ref RxPacketCreate -ed packet to an output of the current node.
 *
 * \ref RxPacketDispatchToPipeline dispatches packets to another pipeline
 * [\ref RxPacketDispatch is used to dispatch packets to one of the outputs
 * of the current node].
 *
 * When \ref RxPacketDispatchToPipeline is called, the packet is sent
 * down the subsequent pipeline immediately due to the nested nature of
 * pipeline execution - when \ref RxPacketDispatchToPipeline is called,
 * the packet is sent down the destination pipeline within this call and
 * will have been destroyed [as described in \ref RxPacketFetch] by the
 * time the dispatch function returns (you can no longer operate on this
 * packet, you will need to \ref RxPacketCreate another).
 *
 * If the current node has not \ref RxPacketCreate -ed or \ref RxPacketFetch -ed a
 * packet then it is valid to pass NULL as the packet parameter to this
 * function (if a packet exists and you simply haven't checked, it will be
 * passed on anyway).
 *
 * As noted in the documentation for \ref RxPacketFetch, it is an error to
 * \ref RxPacketCreate or \ref RxPacketFetch a packet and not to subsequently
 * \ref RxPacketDispatch, \ref RxPacketDispatchToPipeline or \ref RxPacketDestroy
 * it.
 *
 * Note that this function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * \param  packet   A pointer to the packet to dispatch
 * \param  dest   A pointer to the destination pipeline
 * \param  self   A pointer to the calling \ref RxPipelineNode
 *
 * RxPacketFetch is used within node body methods. This example
 * shows the bones of a typical node.
 * \verbatim
   {
           RxPacket *pk;

           pk = RxPacketFetch(self);
           RWASSERT (NULL != pk);

           ... // process packet

           // dispatch packet to output 0 (default)
           RxPacketDispatchToPipeline(pk, fancyPipeline, self);

           return(TRUE);
   }
   \endverbatim
 *
 * \return None
 *
 * \see RxPacketDispatch
 * \see RxPacketFetch
 * \see RxPacketCreate
 * \see RxPacketDestroy
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
void
RxPacketDispatchToPipeline(RxPacket * packet __RWUNUSED__,
                           RxPipeline * dest,
                           RxPipelineNode * self __RWUNUSED__)
{
    RWAPIFUNCTION(RWSTRING("RxPacketDispatchToPipeline"));

    rxPacketDispatchToPipelineMacro(packet, dest, self);

    RWRETURNVOID();
}

#endif /* (defined(DOXYGEN) || defined(RWDEBUG)) */

